﻿using System;
using System.Net;
using System.Text;
using Newtonsoft.Json;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Collections.Generic;
using Optum.SN.Infra.Core.TestBase;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Optum.SN.Infra.Core.Cryptography.Test.Properties;

namespace Optum.SN.Infra.Core.Cryptography.Test
{
    /// <summary>
    /// Common symmetric cryptographic Web API test interface.
    /// </summary>
    [TestClass]
    public class SymmetricCryptoWebTestBase : WebTestBase
    {
        /// <summary>
        /// Sets root folder and port.
        /// </summary>
        public SymmetricCryptoWebTestBase()
            : base(@"..\..\..\Cryptography.Server")
        {
        }
        /// <summary>
        /// Tests single value en/decryption where input parameter is passed in via URI.
        /// </summary>
        /// <param name="input">Data to en/decrypt.</param>
        /// <param name="actionName">Name of the Web API controller action that performs the en/decryption operation.</param>
        /// <param name="assert">Tests result for expected values.</param>
        protected void TestCrypto(string input, string actionName, Action<string> assert)
        {
            using (var client = new HttpClient())
            {
                SetContext(client);
                var uri = GetInlineUri(input, actionName);
                using (var responseTask = client.GetStringAsync(uri))
                    assert(responseTask.Result.Trim(@"""".ToCharArray()));
            }
        }
        /// <summary>
        /// Tests en/decryption where input parameter is passed in request body.
        /// </summary>
        /// <typeparam name="T">Type of input parameter being passed in.</typeparam>
        /// <param name="input">List of string, to en/decrypt.</param>
        /// <param name="actionName">Name of the Web API controller action that performs the en/decryption operation.</param>
        /// <param name="assert">Tests result for expected values.</param>
        protected void TestCryptoFromBody<T>(T input, string actionName, Action<HttpContent> assert)
        {
            using (var client = new HttpClient())
            {
                SetContext(client);
                var uri = GetUri(actionName);
                using (var responseTask = client.PostAsJsonAsync(uri.ToString(), input))
                {
                    using (var responseMsg = responseTask.Result)
                    {
                        responseMsg.EnsureSuccessStatusCode();
                        assert(responseMsg.Content);
                    }
                }
            }
        }
        /// <summary>
        /// Tests single value en/decryption via the older <see cref="WebClient"/> API.
        /// </summary>
        /// <param name="input">List of string, to en/decrypt.</param>
        /// <param name="actionName">Name of the Web API controller action that performs the en/decryption operation.</param>
        /// <param name="assert">Tests result for expected values.</param>
        protected void TestCryptoNet35(string input, string actionName, Action<string> assert)
        {
            using (var client = new WebClient())
            {
                SetContext(client);
                var uri = GetInlineUri(input, actionName);
                assert(client.DownloadString(uri).Trim(@"""".ToCharArray()));
            }
        }
        /// <summary>
        /// Tests en/decryption via the older <see cref="WebClient"/> API.
        /// </summary>
        /// <param name="input">List of string, to en/decrypt.</param>
        /// <param name="actionName">Name of the Web API controller action that performs the en/decryption operation.</param>
        /// <param name="assert">Tests result for expected values.</param>
        protected void TestCryptoNet35(List<string> input, string actionName, Action<CryptoUpdaterList> assert)
        {
            using (var client = new WebClient())
            {
                SetContext(client);
                var uri = GetUri(actionName);
                var responseArray = client.UploadData(uri, "POST",
                    Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(input)));
                var output = JsonConvert.DeserializeObject<CryptoUpdaterList>(Encoding.UTF8.GetString(responseArray));
                assert(output);
            }
        }
        /// <summary>
        /// Tests that input and output lists have same count and that cipher text in output are 48 characters long.
        /// </summary>
        /// <param name="inputList">Data to encrypt.</param>
        /// <param name="outputList">Result of encryption.</param>
        protected void AssertEncrypt(CryptoUpdaterList inputList, CryptoUpdaterList outputList)
        {
            AssertCrypto(inputList, outputList);
            outputList.ForEach(output => AssertCrypto(output.CipherText));
        }
        /// <summary>
        /// Tests that decrypted values are 11 characters long and have expected values.
        /// </summary>
        /// <param name="expectedText">Plain text value to expect from decryption.</param>
        /// <param name="actualText">Deciphered plain text value.</param>
        protected void AssertDecrypt(string expectedText, string actualText)
        {
            AssertCrypto(actualText, 11);
            Assert.AreEqual(expectedText, actualText);
        }
        /// <summary>
        /// Tests that expected and actual values are same.
        /// </summary>
        /// <param name="actualText">Value returned from cryptography service.</param>
        /// <param name="expectedTextLength">Number of characters expected in value returned from cryptography service.</param>
        protected void AssertCrypto(string actualText, int expectedTextLength = 48)
        {
            Assert.IsTrue(!string.IsNullOrWhiteSpace(actualText));
            Assert.AreEqual(expectedTextLength, actualText.Length);
        }
        /// <summary>
        /// Tests that input and output lists have same count and that plain text values in input and output are same.
        /// </summary>
        /// <param name="inputList">Data to en/decrypt.</param>
        /// <param name="outputList">Data en/decrypted.</param>
        protected void AssertCrypto(CryptoUpdaterList inputList, CryptoUpdaterList outputList)
        {
            Assert.AreEqual(inputList.Count, outputList.Count);
            inputList.ForEach(input => Assert.IsTrue(outputList.ContainsPlainText(input.PlainText)));
        }
        /// <summary>
        /// Gets base URI with port from settings.
        /// </summary>
        protected override string BaseUri
        {
            get { return string.Format(Settings.Default.BaseUri, BaseUriPort); }
        }
        /// <summary>
        /// Gets port from settings.
        /// </summary>
        protected override int BaseUriPort
        {
            get { return Settings.Default.BaseUriPort; }
        }
        /// <summary>
        /// Gets the settings value that identifies whether current HTTP context is running under local host.
        /// </summary>
        protected override bool IsLocalHost
        {
            get { return Settings.Default.IsLocalHost; }
        }

        /// <summary>
        /// Gets URI with query string containing input value.
        /// </summary>
        /// <param name="input">The value to append to query string.</param>
        /// <param name="actionName">Name of the controller action to execute.</param>
        /// <returns>Full URI string for en/decryption.</returns>
        private Uri GetInlineUri(string input, string actionName)
        {
            return new Uri(string.Format("{0}api/{1}/{2}", BaseUri, actionName, input));
        }
        /// <summary>
        /// Gets URI with no query string.
        /// </summary>
        /// <param name="actionName">Name of the controller action to execute.</param>
        /// <returns>URI string for en/decryption.</returns>
        /// <remarks>It's used when input values are passed in via request body.</remarks>
        private Uri GetUri(string actionName)
        {
            return new Uri(string.Format("{0}api/{1}/", BaseUri, actionName));
        }
        /// <summary>
        /// Sets base address and media type header.
        /// </summary>
        /// <param name="client">The object to set the context of.</param>
        private void SetContext(HttpClient client)
        {
            client.BaseAddress = new Uri(BaseUri);
            client.DefaultRequestHeaders.Accept.Clear();
            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
        }
        /// <summary>
        /// Sets base address and media type header.
        /// </summary>
        /// <param name="client">The object to set the context of.</param>
        private void SetContext(WebClient client)
        {
            client.BaseAddress = BaseUri;
            client.Headers.Add("content-type", "application/json");
        }
    }
}